Skip to content

WIP: Support >60Hz (2nd try)#585

Draft
DanielGibson wants to merge 23 commits intodhewm:masterfrom
DanielGibson:kickstart-my-hertz
Draft

WIP: Support >60Hz (2nd try)#585
DanielGibson wants to merge 23 commits intodhewm:masterfrom
DanielGibson:kickstart-my-hertz

Conversation

@DanielGibson
Copy link
Member

@DanielGibson DanielGibson commented Jun 27, 2024

Based on @dezo2's patch (see also), but adjusted to make the framerate configurable with the com_gameHz CVar + my own fixes, also some ideas from Stradex' branch.

Very much WIP, and no guarantees this will work out


TODO

Potential bugs that have been observed with various attempts of running dhewm3 with >60Hz and that are worth testing:

  • Crashes with crane in Alpha Labs Sector 3 (should be fixed by patching GAME_FPS and GAME_FRAMETIME in the scripts)
    • Should really be fixed now, was actually something with SetSpeed() in C++ code that I first fixed incorrectly
  • Chaingun shooting too fast (should be fixed by patching CHAINGUN_FIRE_SKIPFRAMES in script)
  • Flickering particles (happened with com_fixedTic -1, might not be relevant here?)
    • Haven't seen this issue so far (and no one else has reported it either)
  • Switching Mods/game modes can cause black menu for multiple seconds to minutes
    • I can still reproduce this bug. I think it's related to counting tics (com_ticNumber?) and then multiplying it with USERCMD_MSEC and comparing the result to Sys_Milliseconds() or something, combined with one mod having a different com_gameHz value than the other, so tics are longer/shorter. Needs more investigation..
      Should be fixed
  • RoE gravity gun only works after 2 minutes or so (haven't tried this yet, might be similar to the previous one)
    • I couldn't reproduce this with my branch, if anyone runs into this problem please report!
  • I found if I tweak the fps higher than the monitor refresh rate, NPCs and mobs in-game will no longer interact with stairs. I think this is not a bug related to this fork, cause I found it in BFG Edition too. (again from same bugreport on simonedibilio's branch - this sounds extremely weird)
    • Update: I don't think this has anything to do with monitor refresh rate, but it seems to happen at >= 250fps (and at >= 200fps or so it already looks quite twitchy). I can reproduce it, see first level T. Washington and Mars Sec Anthony, in section between overhearing Betruger and the restroom door
    • According to dezo2's comment this still happens, esp. at framerates where the frametime is no integer (e.g. with 240fps it happens, with 200 not)
      • It also happens with 333fps (3ms frames) and 250fps (4ms frames), so I don't think this is directly related to integer frametimes, though it could be that at <= 5ms it generally starts to get real glitchy
    • Fixed!
  • Savegames don't work, "Script checksum didn't match"
    • Fixed as far as possible: This update will break savegames, i.e. loading old savegames will make you restart the level the savegames is from. This is due to changes I had to hack into the scripts, can't be avoided.
      But at least (unlike in Stradex/simonedibilio's branch) they don't break every time you change com_gameHz, so from now on they should be stable again.
  • the console cursor (and probably other cursors?) also changes the flashing frequency with different values for com_gameHz
    • fixed
  • The d3xp grabber feels worse at high FPS somehow, for example in that bossfight in erebus2. the grabbed things slow down way too fast and sometimes fall down before being in front of the weapon where they should be held for some time
    • fixed
  • "I saw maybe 2 or 3 physics related glitches where imp couldn't jump out of his hole due to a moving part in the path." "IIRC the glitches were in map recycling1/2 somewhere (can't remember) and in RoE map erebus4 in the beginning where that new imp couldn't leap down from the ceiling."
    • Can be reproduced, see comments below. Not sure if the physics are generally buggy now, or just behave a bit differently (so for example stuff falling to the floor lands at different places and possibly blocks monsters it didn't block before)
    • This seems to be rare enough, and usually the affected monsters turn up eventually, so I could live with this bug
  • Oxygen drainage too slow or fast (should be fixed by modifying the "air" code in idPlayer)
    • Mostly fixed, still TODO: changing com_gameHz while Oxygen is active is buggy
  • Make sure that game frames aren't longer than 17ms, because apparently not only too short frametimes, but also too long frametimes can screw up physics. Just call idGame::RunFrame() multiple times if com_gameHz is too low
  • It would probably make sense to check at runtime if the configured com_gameHz can be reached (and is not slowed down by vsync or simply a slow computer), and show a warning or something if it can't, telling the user to configure a lower value or otherwise fix their settings
  • Getting stuck in delta4 at a fence in the gateway at the very beginning, at >= 173fps (see this comment)
  • Player sometimes getting stuck at stairs (the higher the FPS the worse it is), for example at beginning of hell1, see this comment
  • The Mancubus in hell1 behaves weirdly at 480fps, and "jumps a lot after killing it" (same comment as previous item)
  • Multiplayer must be tested (do clients and server all running at different com_gameHz work?)

@DanielGibson
Copy link
Member Author

This time I was lazy and kept USERCMD_MSEC and USERCMD_HZ around, just turning them into #defines that alias global variables storing the current values.
Not sure how great that really is, but at least it reduces the amount of changed code significantly, as most (but unfortunately not all) code using USERCMD_MSEC/HZ just continues to work without modification.

@DanielGibson
Copy link
Member Author

Ok, I think I have fixed the "Switching game modes can trigger a fake black screen" issue, by fixing how com_frameTime is calculated.
I couldn't reproduce the issue with the grabber either (after the fix, didn't try before).

At least on my (Linux) PC, the framerate is extremely stable now (with vsync disabled), but not at the rate configured with com_gameHz, but the next multiple of int(1000/com_gameHz), e.g. 63fps for 60hz, 125fps for 120hz, 143fps for 144Hz, 250fps for 240Hz.

I guess I should try using floating point (double) times at least for the async tics, but probably I should also try how that all works on Windows and how it interacts with vsync.


However, all this somehow feels wrong: I can't find it right now, but I think there are comments in the code stating that the tics are supposed to be incremented at 60Hz, and that this is supposed to be independent of the render framerate.
But OTOH, they obviously didn't implement (or at least ship) that in Doom3, so maybe they discarded that idea and just forgot to remove some stray comments.

On the other other hand, I don't see the point of that whole async thread for incrementing tics, no matter if the renderer is running in sync with the game or not.
If one wanted to use threads there, it would probably make more sense to run the gamecode and the renderer in different threads, than having an additional thread just for incrementing a counter every 16ms...

@j4reporting
Copy link

the console cursor also changes the flashing frequency with different values for com_gameHz

@DanielGibson
Copy link
Member Author

the console cursor also changes the flashing frequency with different values for com_gameHz

true, I've also noticed this already, will add it to the list (I also know why it happens, just doing other changes first)

@DanielGibson
Copy link
Member Author

DanielGibson commented Jul 2, 2024

I've pushed lots of changes in the last hours.
One thing is that I rebased this on the soft-particle branch, so both features can be tested together (I'll post Windows binaries later).

Another is that the framerates should now be very close to what's configured with com_gameHz (unless VSync or a slow computer slow it down), because the timing of frame starts is now done with much more precision than full milliseconds.

Most times in the game and engine still use full (integer) milliseconds, like before, so I wonder if this causes any issues or if starting the frames at the correct time is good enough for things to run smoothly.

@j4reporting
Copy link

looks goot so far, played up to 1st airlock in mars city underground.

There is an issue with the air supply though. It can be manipulated by changing com_gameHz while being outside

com_gameHz 500 -> cycle airlock then set com_gameHz to a lower value
com_gameHz 20, then again to 10 > 4000

changing com_gameHz to a higher value than configured has ofc the opposide effectl

image

@DanielGibson
Copy link
Member Author

(rebased this branch again, to current master, so it includes the fix for #587)

I'll look at the oxygen issue.
I don't think it's critical though, changing com_gameHz while outside is quite an edge-case ;)

@DanielGibson
Copy link
Member Author

Thanks for testing by the way, I appreciate that you're doing this, and how thorough you are! :)

By the way, does dhewm3 at higher framerates feel smooth?
(Or does your display only support 60Hz anyway so there's not much visual difference, unless a bug introduced additional stuttering?)

@j4reporting
Copy link

It was a coincidence, I changed the setting while i was still in the airlock, but after I already initiated the airlock cycle.
I wonder what other scripted/timed events could be prolonged in this manner?
I guess in the end you'll need a fps independent internal value/timer.
THe same happens with loading a savegame craeated with a different com_gameHz setting.
maybe keep a 60 FPS value around and save only this value in a savegame.

Desktop is set to 144Hz. I would not want to go back to 60Hz.
Higher FPS feel ok. No stutter, nothing feels very good so far. Tested on WIndows with 144Hz and tied 250 for a while as well. Ofc not much is happening at this stage of the game.
Unexpectedly, I had some time to spare, because the Austria - Turkey game was only on a strangly colored pay-TV channel. Sometimes it's an advantage to follow a game via audio stream only.

@DanielGibson
Copy link
Member Author

I guess in the end you'll need a fps independent internal value/timer.

I do, it's just that some code (incl. some scripts) uses the game tics that are incremented each frame, and at a higher framerate they increment faster..
The fixes so far have been to at least scale the amount of tics a function waits (or sets as a limit, for example for "then I'll be out of oxygen") by com_gameHz/60 (=> for 120fps wait for twice as many tics), but as you noticed if com_gameHz are modified after such a "target tic number" has been set, you get glitches.

But maybe I could change the logic to use a floating point tic number and then scale the added value each frame (at 120Hz add 0.5 instead of 1 tic), so the target tic would remain the same.. I just hope this isn't used by too much code besides the oxygen.

Or I'd have to re-evaluate going back to 60Hz tics for the game code and only render at a higher framerate, but I'm not sure if this is feasible (and if there is even a point to that: what use are 120fps if nothing changes every second frame? but maybe something can be done without increasing the tic, I'll have to look at how the prediction code for multiplayer works, it might be related to this..)

nothing feels very good so far

why not?

@j4reporting
Copy link

nothing feels very good so far

why not?

aaaaah. damn.

No stutter, nothing. Feels very good so far.

@DanielGibson
Copy link
Member Author

Here is a build for Windows: dhewm3-1.5.4pre-highfps_win32.zip
Note that it does not support any mods for now, only the base game and RoE.

This contains both the soft particles changes and this high FPS stuff.

Other new features:

  • r_displayRefresh allows setting the display refresh rate for real fullscreen mode (unless you're using Wayland, it doesn't support that)
  • com_showFPS can now be set to 2 to also show avg/min/max frame times in ms

@DanielGibson
Copy link
Member Author

DanielGibson commented Jul 3, 2024

Based on the comment from @dezo2 at #250 (comment) I tested d3xp Erebus4 and yes, that new Imp near the start of the level definitely doesn't spawn (or drop?) like it should when running at 120 or 144 or 240 fps (it still seems to work fine at 60).

It should come out of the black hole that arrow is pointing to:
d3noimp

It eventually is there, at least if I stand directly below that hole, but at 60fps it drops down that hole pretty soon after that panel from the ceiling falls down after entering the room.

Something else I noticed is that things completely fall apart when I change com_gameHz while the game is running (loading a savegame again fixes it).
No matter if I increase or decrease it, and if it's only by a little bit (like 120 -> 121), I get weird moving double images (note that despite the muzzle flash I'm not currently shooting):
d3double

No idea what's going on there, or if the issue is in the scripts or C++ code or whatever.
UPD: That double vision problem seems to be related to me messing with the slowmo state in idGameLocal::SetGameHz().
Still no idea about the Imp (only thing I noticed is that it most probably does spawn, as I heard it hissing).

@dezo2
Copy link

dezo2 commented Jul 3, 2024

I got finally some time before going back to work so I just want to thank you Daniel for working on this. I tested the base game briefly and the game movement seems fine. What I found so far using com_gameHz 200 and 240 with VSync on a 240Hz VRR monitor:

The crane at the start of map alphalabs3 doesn' t come up when I press the button and crashes the game (something about binding an object to itself). Which is strange because the chaingun firing rate is OK, so the scripts should be adjusted.

There is a slight problem with 240Hz using VSync in cutscenes, where the audio sync (lipsync) drifts after a while even with default com_fixedTic 0. This is minor and can be fixed by running the engine at 200Hz (5ms int) - you can test it with map mars_city2 at the start, the survivor up the ladder there speaks relativelly long.

I also noticed very brief frame skips when the Hz does not match the integer framerate like 240Hz and again they dissapear when using 200Hz or by setting com_fixedTic 1. The frequency of the skips seems to match the difference between int and float frametimes so at 240Hz 4.1666ms they are realtivelly far between.

The imp in RoE should come down if you kick the panel under the hole - seems like the spawn is somehow tied to the panel position as it lands differently when it falls down before the imp at FPS >60.

I will continue testing when I have some time.

@dezo2
Copy link

dezo2 commented Jul 3, 2024

The crane crash is not caused by the scripts after all, I tried it with my old code and it needs the SetSteerSpeed adjustment in Physics_AF.h, it was this line in the code:
void SetSteerSpeed(const float speed) { steerSpeed = speed * 60.0f / USERCMD_HZ; }

But I thought you did something similar it in your new code, so not sure what's going on.

@DanielGibson
Copy link
Member Author

DanielGibson commented Jul 3, 2024

Thanks for testing!

seems like the spawn is somehow tied to the panel position as it lands differently when it falls down before the imp at FPS >60

Hmm interesting theory - maybe the panel blocks the spawn position or something?

SetSteerSpeed(const float speed) { steerSpeed = speed * 60.0f / USERCMD_HZ; }

I just realized: I replaced that code with
SetSteerSpeed( const float speed ) { steerSpeed = speed * gameLocal.gameTicScale; }
but gameTicScale is gameHz/60 (the factor for "how many more tics do I need to wait for the same amount of time") - so it must be steerSpeed = speed / gameLocal.gameTicScale; instead.
Same for STOP_SPEED

@DanielGibson
Copy link
Member Author

DanielGibson commented Jul 3, 2024

Just pushed a commit that fixes the crane (I tested the crane and didn't have crashes with 120, 240 or 60fps)

@DanielGibson
Copy link
Member Author

DanielGibson commented Jul 4, 2024

Updated Windows build: dhewm3-1.5.4pre-highfps2_win32.zip
Most important fixes since last build: Crane works now, so does changing framerate in d3xp, cursor blinking speed is normal at all framerates, com_showFPS 2 shows avg/min/max frame times of all frames in last 0.5 seconds.


By the way, some suggestions for testing:

  • Don't forget to increase the framerate ;)
    • In the new menu, which can usually be opened with F10, it's under Game Options -> Framerate
    • Enable com_showFPS to see if the game actually runs at that framerate (VSync might prevent it)
    • You don't necessarily need a display that supports >60Hz. You should not see much of a difference then (compared to how dhewm3 looked/ran before these changes), but most bugs should also be visible when running (without VSync) with 120 (or 144 or 240 or whatever) FPS on a 60Hz screen.
    • And of course testing if 60fps still behave like before is also valuable
  • Save frequently (maybe even increase com_numQuicksaves) and if you run into a bug, make backups of the last one or two quicksaves, which can hopefully help to reproduce it
  • Please report the map an issue happens in, ideally take a screenshot of the place, and run getviewpos or where in the console (and report the output) - it will print at what coordinate in the level you are.

@dezo2
Copy link

dezo2 commented Jul 5, 2024

Tested RoE yesterday with com_gameHz 200 and was able to run from start to finish without problems (apart from that one blocked imp in erebus4). No crashes or game breaking bugs, enemies went up/down the stairs to find me, grabber/slowmo/oxygen/enviro suit/machinery/elevators/bridges all worked as expected, so to me the code is already very usable. I will also try the base game but don't really expect any major problems there. BTW thanks for the F10 hidden menu tip, didn't even know about it.

As for the second blocked imp, it's in the base game, map recycling2 (-494.73 1410.72 -11.75) 190.2, right after interaction with the left screen. Imp should jump out of his closet, but with FPS >= 85 (11ms) he will be blocked with a panel he tried to kick out. So similar situation to than one in RoE. Not sure if this can be fixed without breaking something else in the physics code.
QuickSave.zip
shot00002

@Terepin
Copy link

Terepin commented Jul 6, 2024

I just wanna point out that the original Doom 3 can now run at higher fps than BFG edition. What a time to be alive in.

@dezo2
Copy link

dezo2 commented Jul 6, 2024

I may have found a fix for the blocked imps and potentially other enemies hidden behind wals and panels. This was caused by the STOP_SPEED in physics being too low for higher FPS, so by replacing a line in the source files Physics_RigidBody.cpp (base and d3xp):
const float STOP_SPEED = 10.0f / gameLocal.gameTicScale;
by this line:
const float STOP_SPEED = 10.0f * gameLocal.gameTicScale;
all the blockages seem to be fixed including the imp in RoE erebus4 and the one I posted yesterday. This should also fix the wild enemy ragdols and panels movements after explosions etc. Now at 200 FPS they all look similar to 60 FPS. Hopefully this won't break anything else. I will continue testing with this but I think this can be it.

@dezo2
Copy link

dezo2 commented Jul 6, 2024

Crap, celebrated too soon. It works with 200 FPS but not with 144 or 120. The physics code is seriously weird. The STOP_SPEED has to be the culprit for these problems.

@markanini
Copy link

I think I've found a unique bug in this branch, so I'll report here. Quick-saving during the PDA pickup notification will cause a persistent erroneous graphic as seen in the screenshot.
shot00014

@lukeman3000
Copy link

Hi Daniel; I was just curious where you're at with this - is there any chance we might get upcapped fps without physics problems or do you think this will be quite difficult if not impossible?

The changes in this branch breaks the Game API,
so it won't be in a 1.5.x release.
…ord"

This reverts commit 2e0b093 so
the "softeningRadius" keyword in Particle Stages, as defined by
TheDarkMod, is supported.

This breaks the game API!
The CVar com_gameHz can be used to set the desired framerate, so we're
no longer stuck to hardcoded 60fps.
To minimize the amount of code that needs changes, USERCMD_HZ and
USERCMD_MSEC are now #defines to values holding the corresponding values
based on the current com_gameHz value.
The game DLLs (need to) have their own variables for this, and those are
set through the new idGame::SetGameHz() (yes, this means the game API
will be broken; I guess that's unavoidable for this feature anyway).

This is just the first step, there's more code (that currently assumes
16ms ticks) that will be adjusted in additional commits, and the scripts
also need a similar hack for their GAME_FPS, GAME_FRAMETIME and
CHAINGUN_FIRE_SKIPFRAMES #defines.
but most of that commit is not needed anymore because it's already
in master and better than before

+ small adjustment to common->Frame() where I hardcoded 60 (Hz)
  in one place
Note: A simpler approach would be replacing "#define GAME_FPS 60" and
the similar defines for GAME_FRAMETIME and CHAINGUN_FIRE_SKIPFRAMES
in the scripts with other values based on com_gameHz, for example to
"#define GAME_FPS 144".

However, that would have two disadvantages:
1. When changing com_gameHz, you'll have to reload the scripts
2. This changes the checksum of the scripts, so each time you change
   com_gameHz the checksum changes and your old savegames stop working.

Luckily the scripts already have functions (that are implemented in the
gamecode) that expose the FPS and FrameTime: sys.getTicksPerSecond()
 and sys.getFrameTime()

So now we modify the #defines to call those functions instead.
That will still break *old* savegames, but at least savegames made from
now on will still work no matter what you set com_gameHz to.

TODO: A problem with this approach is that the time sys.getFrameTime()
returns is scaled when in slow motion, so I'll probably have to add
another function for that

This code is partly based on code from Stradex' "Add com_gameHz to play
 with higher HZ/FPS" PR: dhewm#297
This fixes the issue of scaled frametimes in d3xp.

As for some reason the events defined in C++ code must be defined again
in scripts (script/doom_events.script), I had to add a little hack to
the script compiler to inject the definition for getRawFrameTime()
(it's done when the getFrameTime definition is parsed)
adjusted to use gameLocal.gameTicScale instead of 60/USERCMD_HZ
I turned idPlayer::airTics into a float (so they are now virtual
 60Hz tics instead of actual number of tics) and adjusted the code
that increases or decreases the airTics (idPlayer::UpdateAir())
to scale the values added/subtracted accordingly.
also do that when loading a savegame, so those values are correct
even if com_gameHz has a different value than it had when saving
it uses com_ticNumber to decide when to (not) draw - that must be scaled
for shorter tics (at higher FPS)
turns out ResetSlowTimeVars() also does things one absolutely does
*not* want at that point, like resetting frame counters and times
that are *always* used, no matter if slowmo is enabled or not
in preparation of trying out TDM's fixes
they have *lots* of changes to physics code, partly to support mantling
and movement in water, so it's not that easy to tell which are relevant
for us. These are in things already identified as unstable here, so
I'm hopeful that they help..
its steer speed must be adjusted for frame length
many thanks to dezo2 for suggesting these fixes!

I think in the StepMove() case it's more of a hack than a fix - *maybe*
the correct thing would be to accumulate the deltas over multiple frames
when you got blocked, or something like this? - but as long as it
doesn't break anything else I don't care..
at high framerates, grabbed stuff often fell down before being in front
of the Grabber (where it could be thrown away again)

Thanks to @dezo2 (again!) for pushing me in the right direction!
it's still possible, but a warning is shown and the setting in the
menu only goes up to 250 now.
Apart from physics getting wonkier the higher the framerate is, >250Hz
breaks slow motion effects in RoE (d3xp), because it divides frametimes
by four, and as the frametimes are integers, for >250Hz they're <= 3,
so divided by four that's 0, which isn't good.

Furthermore, I moved that setting to the Video settings, so it's closer
to related settings like VSync and display refreshrate.
While at it, I made sure that `com_showFPS 2` can be configured in the
menu as well (it still assumed it's a bool).

Last but not least I fixed a misleading variable name in Win_InitTime()
to prevent them from jumping so much at high framerates
So far dhewm3 ignored that CVar, now it tries to set that refreshrate

Also display the current refreshrate in the Dhewm3SettingsMenu, but
no way to configure it there yet.
@DanielGibson
Copy link
Member Author

I rebased this branch to current master.
Shouldn't make much of a difference, except that maybe it runs a bit smoother due to changes done there (like not using the async thread to calculate frame times anymore) - and of course other non-framerate related features from the master branch are available here now, like better MD3 support.

Doing this wasn't trivial, because I merged and then changed some parts of this branch to master for the "smoother timing at 60Hz" feature, so it's possible that I broke something - testing is welcome! :)

Hi Daniel; I was just curious where you're at with this - is there any chance we might get upcapped fps without physics problems or do you think this will be quite difficult if not impossible?

I don't know.

@ElTioRata
Copy link

ElTioRata commented Feb 8, 2026

Not sure why but this branch doesn't work with Sikkmod, game crashes after trying to load the opening FMV. 1.5.5 RC2 works okay.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.